<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue DSL modelValue 错误解决方案</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        font-family: 'Segoe UI', system-ui, sans-serif;
      }

      body {
        background: linear-gradient(135deg, #f5f7fa, #e4edf9);
        color: #2c3e50;
        min-height: 100vh;
        padding: 30px;
        line-height: 1.6;
      }

      .container {
        max-width: 1000px;
        margin: 0 auto;
      }

      header {
        text-align: center;
        margin-bottom: 40px;
      }

      h1 {
        font-size: 2.5rem;
        background: linear-gradient(45deg, #4361ee, #3a0ca3);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        margin-bottom: 15px;
        font-weight: 700;
      }

      .subtitle {
        color: #4a5568;
        font-size: 1.1rem;
        max-width: 700px;
        margin: 0 auto;
      }

      .panel {
        background: white;
        border-radius: 12px;
        box-shadow: 0 10px 35px rgba(0, 0, 0, 0.1);
        padding: 30px;
        margin-bottom: 30px;
      }

      .panel-header {
        display: flex;
        align-items: center;
        margin-bottom: 25px;
        padding-bottom: 15px;
        border-bottom: 2px solid #4361ee;
      }

      .panel-header h2 {
        color: #3a0ca3;
        font-size: 1.8rem;
      }

      .badge {
        background: #4361ee;
        color: white;
        padding: 3px 12px;
        border-radius: 20px;
        font-size: 0.9rem;
        margin-left: 15px;
        font-weight: 600;
      }

      .dsl-builder {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 30px;
        margin-top: 25px;
      }

      @media (max-width: 768px) {
        .dsl-builder {
          grid-template-columns: 1fr;
        }
      }

      .code-editor {
        background: #011627;
        color: #d6deeb;
        border-radius: 8px;
        padding: 20px;
        font-family: 'Fira Code', Consolas, monospace;
        overflow-y: auto;
        max-height: 400px;
        position: relative;
      }

      .code-editor::before {
        content: 'DSL 配置';
        position: absolute;
        top: -12px;
        left: 15px;
        background: #011627;
        color: #82aaff;
        padding: 0 10px;
        font-size: 14px;
        border-radius: 4px;
      }

      .preview-area {
        min-height: 300px;
        padding: 25px;
        border: 2px dashed #4361ee66;
        border-radius: 10px;
        background: #f9fafe;
        position: relative;
      }

      .preview-area::before {
        content: '渲染结果';
        position: absolute;
        top: -12px;
        left: 15px;
        background: #4361ee;
        color: white;
        padding: 0 10px;
        font-size: 14px;
        border-radius: 4px;
      }

      .form-group {
        margin-bottom: 20px;
      }

      label {
        display: block;
        margin-bottom: 8px;
        font-weight: 600;
        color: #3a0ca3;
      }

      .input-field {
        width: 100%;
        padding: 12px 15px;
        border: 2px solid #4361ee66;
        border-radius: 8px;
        font-size: 1rem;
        transition: all 0.3s ease;
      }

      .input-field:focus {
        outline: none;
        border-color: #4361ee;
        box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.25);
      }

      .btn {
        padding: 12px 25px;
        border: none;
        border-radius: 8px;
        background: #4361ee;
        color: white;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s ease;
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        box-shadow: 0 4px 6px rgba(67, 97, 238, 0.2);
      }

      .btn:hover {
        background: #3a0ca3;
        transform: translateY(-3px);
        box-shadow: 0 7px 14px rgba(58, 12, 163, 0.25);
      }

      .btn i {
        margin-right: 8px;
      }

      .data-display {
        background: #f0f4ff;
        border-radius: 8px;
        padding: 20px;
        margin-top: 20px;
      }

      .data-display h3 {
        color: #4361ee;
        margin-bottom: 15px;
      }

      .data-display pre {
        background: #011627;
        color: #d6deeb;
        padding: 15px;
        border-radius: 6px;
        overflow-x: auto;
      }

      .dsl-component {
        margin: 15px 0;
        padding: 15px;
        border-left: 3px solid #4361ee;
        background: #f0f4ff;
        border-radius: 0 8px 8px 0;
      }

      .explanation {
        background: #f0f8ff;
        border-radius: 8px;
        padding: 20px;
        margin-top: 25px;
      }

      .explanation h3 {
        color: #4361ee;
        margin-bottom: 15px;
        font-size: 1.3rem;
      }

      .explanation pre {
        background: #011627;
        color: #d6deeb;
        padding: 15px;
        border-radius: 6px;
        overflow-x: auto;
        margin-top: 10px;
      }

      .footer {
        text-align: center;
        margin-top: 40px;
        color: #718096;
        padding: 20px;
      }

      .form-row {
        display: flex;
        gap: 15px;
        margin-bottom: 15px;
      }

      .form-row > div {
        flex: 1;
      }

      .input-container {
        margin-bottom: 20px;
      }

      .input-container label {
        display: block;
        margin-bottom: 5px;
        font-weight: 600;
        color: #3a0ca3;
      }

      .input-container input {
        width: 100%;
        padding: 10px;
        border: 2px solid #4361ee66;
        border-radius: 6px;
        font-size: 1rem;
      }

      .stats {
        display: flex;
        justify-content: space-between;
        margin-top: 20px;
        padding-top: 20px;
        border-top: 1px solid #e2e8f0;
      }

      .stat-item {
        text-align: center;
      }

      .stat-value {
        font-size: 1.8rem;
        font-weight: 700;
        color: #3a0ca3;
      }

      .stat-label {
        font-size: 0.9rem;
        color: #718096;
      }

      .error-fix {
        background: #fff8f8;
        border: 1px solid #ffcccc;
        border-radius: 8px;
        padding: 20px;
        margin-top: 20px;
      }

      .error-fix h3 {
        color: #e53e3e;
        margin-bottom: 15px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="container">
        <header>
          <h1>Vue DSL modelValue 错误解决方案</h1>
          <p class="subtitle">修复 "Cannot create property 'value' on string" 错误</p>
        </header>

        <div class="panel">
          <div class="panel-header">
            <h2>问题演示</h2>
            <span class="badge">错误示例</span>
          </div>

          <div class="error-fix">
            <h3>错误原因分析</h3>
            <p>错误发生在尝试在字符串原始值上设置属性：</p>
            <pre>
// 错误代码
const username = '初始用户名';

const usernameInput = new InputBuilder()
  .modelValue(username) // 错误：直接传递字符串原始值
  .build();

// 在组件内部尝试设置属性
username.value = '新用户名'; // 错误：Cannot create property 'value' on string</pre
            >
          </div>

          <div class="dsl-builder">
            <div class="code-editor">
              <pre>
// 错误的使用方式
const username = '初始用户名'; // 字符串原始值

const usernameInput = new InputBuilder()
  .size('large')
  .type('text')
  .placeholder('输入用户名')
  .modelValue(username) // 错误：直接传递字符串原始值
  .build();</pre
              >
            </div>

            <div class="preview-area">
              <div class="input-container">
                <label>用户名</label>
                <component :is="errorComponent" />
              </div>
            </div>
          </div>
        </div>

        <div class="panel">
          <div class="panel-header">
            <h2>正确解决方案</h2>
            <span class="badge">修复方案</span>
          </div>

          <div class="dsl-builder">
            <div class="code-editor">
              <pre>
// 正确的使用方式
const username = ref('初始用户名'); // 使用 ref 创建响应式引用

const usernameInput = new InputBuilder()
  .size('large')
  .type('text')
  .placeholder('输入用户名')
  .modelValue(username) // 正确：传递 ref 对象
  .build();</pre
              >
            </div>

            <div class="preview-area">
              <div class="input-container">
                <label>用户名</label>
                <component :is="fixedComponent" />
              </div>

              <div class="stats">
                <div class="stat-item">
                  <div class="stat-value">{{ changeCount }}</div>
                  <div class="stat-label">更改次数</div>
                </div>
              </div>
            </div>
          </div>

          <div class="error-fix">
            <h3>修复方法</h3>
            <p>在 DSL 构建器中添加类型检查，确保只接受 ref 对象：</p>
            <pre>
class InputBuilder {
  // ...
  
  modelValue(valueRef) {
    // 检查是否为 ref 对象
    if (!valueRef || typeof valueRef !== 'object' || !('value' in valueRef)) {
      throw new Error('modelValue 必须传入一个 ref 对象');
    }
    
    this._valueRef = valueRef;
    return this;
  }
  
  // ...
}</pre
            >
          </div>
        </div>

        <div class="panel">
          <div class="panel-header">
            <h2>完整 DSL 实现</h2>
            <span class="badge">修复后的代码</span>
          </div>

          <div class="explanation">
            <h3>修复后的 DSL 构建器类</h3>
            <pre>
class InputBuilder {
  constructor() {
    this._size = 'medium';
    this._type = 'text';
    this._placeholder = '';
    this._valueRef = null;
    this._events = {};
  }
  
  size(size) {
    this._size = size;
    return this;
  }
  
  type(type) {
    this._type = type;
    return this;
  }
  
  placeholder(placeholder) {
    this._placeholder = placeholder;
    return this;
  }
  
  // 修复后的 modelValue 方法
  modelValue(valueRef) {
    // 添加类型检查
    if (!valueRef || typeof valueRef !== 'object' || !('value' in valueRef)) {
      throw new Error('modelValue 必须传入一个 ref 对象');
    }
    
    this._valueRef = valueRef;
    return this;
  }
  
  on(event, handler) {
    this._events[event] = handler;
    return this;
  }
  
  build() {
    const self = this;
    
    return {
      setup() {
        // 使用传入的 ref 对象
        const value = self._valueRef;
        
        return {
          size: self._size,
          type: self._type,
          placeholder: self._placeholder,
          value,
          events: self._events
        };
      },
      render() {
        return h('input', {
          class: [
            'dsl-input',
            `input-${this.size}`
          ],
          type: this.type,
          placeholder: this.placeholder,
          value: this.value.value,
          onInput: (e) => {
            const target = e.target;
            this.value.value = target.value;
            
            // 触发自定义 input 事件
            if (this.events.input) {
              this.events.input(e);
            }
          },
          onChange: (e) => {
            // 触发自定义 change 事件
            if (this.events.change) {
              this.events.change(e);
            }
          },
          onFocus: (e) => {
            // 触发自定义 focus 事件
            if (this.events.focus) {
              this.events.focus(e);
            }
          },
          onBlur: (e) => {
            // 触发自定义 blur 事件
            if (this.events.blur) {
              this.events.blur(e);
            }
          }
        });
      }
    };
  }
}</pre
            >
          </div>
        </div>

        <div class="panel">
          <div class="panel-header">
            <h2>使用示例</h2>
            <span class="badge">正确用法</span>
          </div>

          <div class="explanation">
            <h3>正确使用 DSL 构建器</h3>
            <pre>// 创建 ref 对象
const username = ref('初始用户名');
const email = ref('user@example.com');

// 创建 DSL 组件
const usernameInput = new InputBuilder()
  .size('large')
  .type('text')
  .placeholder('输入用户名')
  .modelValue(username) // 传入 ref 对象
  .on('change', () => console.log('用户名已更改'))
  .build();

const emailInput = new InputBuilder()
  .size('medium')
  .type('email')
  .placeholder('输入邮箱')
  .modelValue(email) // 传入 ref 对象
  .on('blur', () => console.log('邮箱输入框失去焦点'))
  .build();

// 在应用中渲染组件
createApp({
  components: {
    UsernameInput: usernameInput,
    EmailInput: emailInput
  },
  setup() {
    return {
      username,
      email
    };
  },
  template: `
    <div>
      <UsernameInput />
      <EmailInput />
    </div>
  `
}).mount('#app');</pre>
          </div>
        </div>

        <div class="footer">
          <p>
            Vue DSL modelValue 错误解决方案 | 修复 "Cannot create property 'value' on string" 错误
          </p>
        </div>
      </div>
    </div>

    <script>
      const { createApp, ref, h } = Vue

      // 错误版本的 DSL 构建器（会导致错误）
      class ErrorInputBuilder {
        constructor() {
          this._size = 'medium'
          this._type = 'text'
          this._placeholder = ''
          this._value = ''
          this._events = {}
        }

        size(size) {
          this._size = size
          return this
        }

        type(type) {
          this._type = type
          return this
        }

        placeholder(placeholder) {
          this._placeholder = placeholder
          return this
        }

        // 错误：直接接受原始值
        modelValue(value) {
          this._value = value
          return this
        }

        on(event, handler) {
          this._events[event] = handler
          return this
        }

        build() {
          const self = this

          return {
            setup() {
              // 错误：尝试在字符串上设置 value 属性
              return {
                size: self._size,
                type: self._type,
                placeholder: self._placeholder,
                value: self._value, // 这里是字符串，不是 ref
                events: self._events,
              }
            },
            render() {
              return h('input', {
                value: this.value.value, // 这里会导致错误
                onInput: (e) => {
                  // 尝试在字符串上设置 value 属性
                  this.value.value = e.target.value
                },
              })
            },
          }
        }
      }

      // 修复后的 DSL 构建器
      class FixedInputBuilder {
        constructor() {
          this._size = 'medium'
          this._type = 'text'
          this._placeholder = ''
          this._valueRef = null
          this._events = {}
        }

        size(size) {
          this._size = size
          return this
        }

        type(type) {
          this._type = type
          return this
        }

        placeholder(placeholder) {
          this._placeholder = placeholder
          return this
        }

        // 修复后的 modelValue 方法
        modelValue(valueRef) {
          // 添加类型检查
          if (!valueRef || typeof valueRef !== 'object' || !('value' in valueRef)) {
            throw new Error('modelValue 必须传入一个 ref 对象')
          }

          this._valueRef = valueRef
          return this
        }

        on(event, handler) {
          this._events[event] = handler
          return this
        }

        build() {
          const self = this

          return {
            setup() {
              // 使用传入的 ref 对象
              const value = self._valueRef

              return {
                size: self._size,
                type: self._type,
                placeholder: self._placeholder,
                value,
                events: self._events,
              }
            },
            render() {
              return h('input', {
                class: ['dsl-input', `input-${this.size}`],
                type: this.type,
                placeholder: this.placeholder,
                value: this.value.value,
                onInput: (e) => {
                  const target = e.target
                  this.value.value = target.value

                  // 触发自定义 input 事件
                  if (this.events.input) {
                    this.events.input(e)
                  }
                },
                onChange: (e) => {
                  // 触发自定义 change 事件
                  if (this.events.change) {
                    this.events.change(e)
                  }
                },
                onFocus: (e) => {
                  // 触发自定义 focus 事件
                  if (this.events.focus) {
                    this.events.focus(e)
                  }
                },
                onBlur: (e) => {
                  // 触发自定义 blur 事件
                  if (this.events.blur) {
                    this.events.blur(e)
                  }
                },
              })
            },
          }
        }
      }

      // 创建 Vue 应用
      const app = createApp({
        setup() {
          // 错误示例数据
          const errorValue = '初始用户名'

          // 修复后数据
          const fixedValue = ref('初始用户名')
          const changeCount = ref(0)

          // 错误组件
          const errorComponent = new ErrorInputBuilder()
            .size('large')
            .type('text')
            .placeholder('输入用户名')
            .modelValue(errorValue)
            .build()

          // 修复后组件
          const fixedComponent = new FixedInputBuilder()
            .size('large')
            .type('text')
            .placeholder('输入用户名')
            .modelValue(fixedValue)
            .on('input', () => changeCount.value++)
            .build()

          return {
            errorComponent,
            fixedComponent,
            fixedValue,
            changeCount,
          }
        },
      })

      app.mount('#app')
    </script>
  </body>
</html>
